/**
* jsgig - JavaScript Get-It-Going.
*
* Depends: jQuery, util
*
* -- Summary --
* 
* JSGIG, pronounced "Jay Ess Gig", is a JavaScript library intended for the
* purpose of quickly creating 2D sprite-based games in a browser window.
* 
* -- Basic Usage -- 
* 
* The main source file that needs to be included for JSGIG is "jsgig/jsgig.js".
* Before you do this, however, you must first include the accompanying "util.js"
* utiliy library, as this is used by JSGIG to include the remaining source
* files.
* 
* JSGIG currently depends on JQuery. If you do not manually include JQuery
* before including JSGIG, it will attempt to include "jsgig/../jquery.js" using
* util.include().
*  
* You can set up and run the engine in one of two ways. First, you can use the
* all-in-one jsgig.getItGoing() function, which accepts all data that the game 
* will use as a single object and provides a very fast way to just "get it 
* going." Second, you can use the individual functions that getItGoing() 
* calls, which allows a finer degree of control but requires more work. (This
* will be discussed later.)
* 
* The following HTML file provides a basic shell of a game that simply displays
* a black screen:
* 
* <!DOCTYPE html>
* <html>
*   <head>
*     <script src="util.js"></script>
*     <script>
*       util.include("jsgig.js");
*     </script>
*     <script>
*       jsgig.getItGoing({
*         onStart: function() {
*           // Game intialization code; called after JSGIG internals are
*           // already set up
*         },
*         onUpdate: function() {
*           // Game update code; called once per game cycle (per frame)
*         },
*       })
*     </script>
*   </head>
*   <body>
*   </body>
* </html>
* 
* It is important to make sure that no part of JSGIG be used in the same
* script element from which JSGIG was included, even when using the
* util.include().
* 
* See the documentation for getItGoing() for more details about how to use it.
* 
* -- Advanced Usage --
* 
* TODO: Cover manual set-up and running of the engine.
*
* -- Orginization -- 
* 
* JSGIG is broken up into a set of modules. The development version has each
* of these modules in a separate file under the `jsgig` folder, with each file
* being named after the module contained within, followed by the extension
* ".js". The main `jsgig` module is contained within the file "jsgig/jsgig.js".
* As stated earlier, however, the production version contains all code for the
* library in a single minified JavaScript file which can be included by
* itself or embedded directly into the HTML document.
* 
* All modules are accessed as submodules of the main `jsgig` module. They are
* summarized below:
* 
*  jsgig         -- Main module defining core functionality.
*  jsgig.tree    -- Implementation of generic tree-like structures.
*  jsgig.screen  --
*  jsgig.event   --
*  jsgig.entity  --
*  jsgig.vi      --
*  jsgig.res -- 
* 
* For now, assume that ALL modules are required and must be included in the
* order listed above. In the future, it may be possible to only include the
* desired modules.
* 
* Every module file is written to allow for it to be safely included multiple
* times. Only the first include will actually have an effect; subsequent
* includes will do nothing.
* 
* -- Classes --
* 
* JSGIG makes heavy use of the class-like functions that are produced through
* the use of the `util.extend()` function. These functions will generally be
* referred to as "classes" throughout the documentation. Classical ideas such
* as inheritence and polymorphism are followed consistently throughout the
* library.
* 
* Games that use JSGIG would do well to follow its pattern and use the
* `util.extend()` function for classes. This is by far the easiest way
* to extend the classes provided by JSGIG, and is also a fairly easy way to
* create new base classes.
* 
* NOTE: The `util` module is not considered a part of JSGIG, but was created
* in tandem with JSGIG primarily to fill certain needs of JSGIG and its host
* project, Antispace-JS.
*
* -- Internal Coding Conventions --
*
* When browsing the source code of any module you will encounter a variable
* called `my`. This variable is shorthand for the current module and is used
* for brevity and to make it easier to change the module name if needed later.
* 
* Module functions are defined using a specific syntax:
* 
*  my.functionName = function functionName(...) {
*    // ...
*  }
* 
* Though it is redundant to give the name of the function twice, doing so
* makes debugging easier because it allows the function to be accessed by its
* name within the module while at the same time allowing the name of the
* function to appear in stack traces.
* 
* You will also encounter various variables and functions that begin with an 
* _underscore and are not bound to the `my` object. These are private to the 
* module and cannot be accessed except by functions defined within the module. 
* Access to the values of these variables is only possible through the use of 
* module functions.
* 
* -- Further Documentation -- 
* 
* Documentation for the library's modules is contained within the files for
* each module. Each source file begins with a JavaDoc-style comment header that
* explains the module's purpose, usage, and dependencies. Each function of the
* module will also have a JavaDoc header before it that explains that
* particular function.
* 
* -- Planning and Development --
* 
* There is currently no full-on written plan for JSGIG located in any one
* place. Generally speaking, new features are planned or added based on the
* needs of Antispace-JS, which itself is only loosely planned.
* 
* I tend to use documentation as a planning aid. That is, I will write the
* documentation for a feature first, then implement the feature afterwords
* using the documentation as a guide, then modify the documentation afterwards
* to reflect what I actually implemented. If a conflict exists between the
* documentation and the code, you'll have to check the revision logs to
* determine whether the documentation is a plan for how the code is going to be
* or else just outdated.
*/
if (!jsgig) { var jsgig = (function(my) {

if (!util) { throw new Error("util must be included before JSGIG."); }

// INCLUDES //
// Note: These do not take effect until AFTER the module is initialized.

util.include(
  "tree.js",
  "screen.js",
  "event.js",
  "entity.js",
  "vi.js",
  "res.js",
  "image.js"
);

if (window.jQuery===undefined)
  util.include("../jquery.js");

// Private constants

var _FRAMERATE = 30;

// Private variables

var _opts;
var _init = false;
var _framerate = _FRAMERATE;
var _space = null; // 
var _updateInterval = null;
var _startHooks = [];
var _cycleHooks = [];
var _stopHooks = [];

// Public constants

// Keycodes
my.KEYS = {
  backspace: 8,
  tab: 9,
  enter: 13,
  shift: 16,
  ctrl: 17,
  alt: 18,
  pause: 19,
  capslock: 20,
  escape: 27,
  space: 32,
  pageup: 33,
  pagedown: 34,
  end: 35,
  home: 36,
  left: 37,
  up: 38,
  right: 39,
  down: 40,
  insert: 45,
  del: 46,
  n0: 48,
  n1: 49,
  n2: 50,
  n3: 51,
  n4: 52,
  n5: 53,
  n6: 54,
  n7: 55,
  n8: 56,
  n9: 57,
  a: 65,
  b: 66,
  c: 67,
  d: 68,
  e: 69,
  f: 70,
  g: 71,
  h: 72,
  i: 73,
  j: 74,
  k: 75,
  l: 76,
  m: 77,
  n: 78,
  o: 79,
  p: 80,
  q: 81,
  r: 82,
  s: 83,
  t: 84,
  u: 85,
  v: 86,
  w: 87,
  x: 88,
  y: 89,
  z: 90,
  lwin: 91,
  rwin: 92,
  select: 93,
  np0: 96,
  np1: 97,
  np2: 98,
  np3: 99,
  np4: 100,
  np5: 101,
  np6: 102,
  np7: 103,
  np8: 104,
  np9: 105,
  npmultiply: 106,
  npadd: 107,
  npsubtract: 109,
  npdot: 110,
  npdivide: 111,
  f1: 112,
  f2: 113,
  f3: 114,
  f4: 115,
  f5: 116,
  f6: 117,
  f7: 118,
  f8: 119,
  f9: 120,
  f10: 121,
  f11: 122,
  f12: 123,
  numlock: 144,
  scrolllock: 145,
  semicolon: 186,
  equals: 187,
  comma: 188,
  dash: 189,
  period: 190,
  slash: 191,
  accent: 192,
  lbracket: 219,
  backslash: 220,
  rbracket: 221,
  apos: 222
}

// Public variables
// (none currently)

// System Functions

/**
* All-in-one game setup function.
* 
* Depends: jsgig.screen, jsgig.event
* 
* This function can be used to quickly establish a number of general settings
* that most games require, such as the virtual screen size, frame rate, virtual
* inputs (player controls), and so on. It is also a place where you can specify
* functions to run once the game engine is fully initialized and during
* different parts of the game logic cycle.
* 
*  jsgig.getItGoing({
*    option0: value0,
*    option1: value1,
*    ...
*    optionN: valueN
*  })
*     
* The complete set of recognized options for getItGoing() and their default
* values are as follows:
*
*   {
*     container: "body",
*     // A jQuery selector for the screen container.
*     
*     framerate: 30,
*     // Number of logic cycles and screen updates (frames) per second.
*     
*     size: [640,480],
*     // Size of the virtual screen. This will be the reported width of the
*     // screen element in pixels, though the actual amount of monitor space
*     // consumed by the screen may be different (see `scale` below).
* 
*     scale: 1,
*     // Scale factor for the screen (applied to both dimensions). 0 means
*     // the screen should be scaled to fit the container, maintaining aspect 
*     // ratio. This only works reliably if the container is the document
*     // body.    
*
*     images: [],
*     // A collection of the URLs of images to begin preloading.
* 
*     keymap: {
*         left: KEYS.left,
*         right: KEYS.right,
*         up: KEYS.up,
*         down: KEYS.down,
*         a: KEYS.z,
*         b: KEYS.x,
*         start: KEYS.enter,
*         select: KEYS.shift
*       },
*     // The default keymap for the first player's virtual input device (VID). 
*     // This will be assigned to jsgig.vi[0]. If you do not want to create a
*     // VID, pass null.
* 
*     onStart: null,
*     // A function to call just before the engine cycle starts. 
* 
*     onPreupdate: null,
*     // A function to call just before the update portion of the game cycle.
*         
*     onUpdate: null,
*     // A function to call just after the update portion of the game cycle.
* 
*     onPredraw: null,
*     // A function to call just before the draw potion of the game cycle.
* 
*     onDraw: null,
*     // A function to call just after the draw portion of the game cycle.
* 
*     onStop: null
*     // A function to call just after the engine cycle stops. 
*   }
* 
* getItGoing() internally uses jQuery's $(document).ready() to intialize
* JSGIG's subsystems and start the game, so do not use $(document).ready()
* yourself if you use getItGoing(). If you do want to use $(document).ready() 
* yourself, you must manually initialize the various subsystems of JSGIG
* rather than use getItGoing().
* 
* If you wish to manually set up the game in a manner similar to getItGoing(),
* the following list gives a brief overview of the steps that getItGoing() goes
* through:
* 
* - Verify that required modules have been included, (ex. `if (!jsgig.screen)`)
* - Register a function to $(document).ready() to handle the remaining work once
*   the DOM is fully loaded.
* 
* Once DOM is loaded:
* 
* - Set framerate, if one was passed (ex. `jsgig.setFramerate()`)
* - 
*/
my.getItGoing = function getItGoing(opts) {
  if (!_init) {
    // Most of the work done by this function is just to verify that everything
    // is included. That majority of the actual work of setting up the game is
    // performed by _getItGoingOnReady().
    if (!my.screen)
      throw new my.JSGIGError(
        "Game setup was halted because module jsgig.screen is not available. " +
        "Please verify that the file 'jsgig/screen.js' was included. "
      );
    if (!my.event)
      throw new my.JSGIGError(
        "Game setup was halted because module jsgig.event is not available. " +
        "Please verify that the file 'jsgig/event.js' was included. "
      );
    if (!my.res)
      throw new my.JSGIGError(
        "Game setup was halted because module jsgig.res is not available. " +
        "Please verify that the file 'jsgig/res.js' was included. "
      );
    _opts = opts;
    $(document).ready(_getItGoingOnReady);
  }
}

// This is the function that ACTUALLY gets it going.
function _getItGoingOnReady(e) {
  var i, opts = _opts;
  _opts = null;
  
  // core
  if (opts.framerate)
    my.setFramerate(opts.framerate);
  my.init();
  
  // hooks
  if (opts.onStart)
    my.addStartHook(opts.onStart);
  if (opts.onPreupdate)
    my.addCycleHook(opts.onPreupdate, my.update, true);
  if (opts.onUpdate)
    my.addCycleHook(opts.onUpdate, my.update);
  if (opts.onPredraw)
    my.addCycleHook(opts.onPredraw, my.draw, true);
  if (opts.onDraw)
    my.addCycleHook(opts.onDraw, my.draw);
  if (opts.onStop)
    my.addStopHook(opts.onStop);

  // screen
  if (opts.container)
    my.screen.setContainer(opts.container);
  if (opts.size)
    my.screen.setSize(opts.size);
  if (opts.scale !== undefined)
    my.screen.setScale(opts.scale);
  if (opts.bgcolor)
    my.screen.setBGColor(opts.bgcolor);
  my.screen.create();
  
  // events
  my.event.init();
  
  // resource management
  my.res.init();
  if (opts.images) {
    var images = opts.imgages;
    for (i in images) {
      my.res.image(images[i]);
    }
  }
  
  // user control
  if (opts.keymap === null)
    { /* leave blank */ }
  else if (opts.keymap === undefined) {
    my.vi.createDefault();
  }
  else {
    new my.vi.VirtualInputDevice(opts.keymap); 
  }
  
  // Start the engine!
  my.start();
}

/**
* Set up core components of the engine.
*
* There are many things that will not work correctly until this function is
* called. Unless a function explicitly states that it will work before
* `jsgig.init()` is called, assume that `init()` must be called first.
*
* NOTE: jsgig.getItGoing() will call this.
* 
*   jsgig.init();
*/
my.init = function init() {
  if (!_init) {
    _init = true;
    _space = new my.entity.Space();
    my.addCycleHook(my.event.triggerAll);
    my.addCycleHook(my.update);
    my.addCycleHook(my.draw);
  }
}

/**
* Starts or re-starts the engine.
* 
* Start hooks are called just before the cycle is actually started to make sure
* that no part of the actual engine cycle occurs while the hooks are still
* running.
* 
* If the engine is already running at the time that this is called, the engine
* will be stopped and all stop hooks will be triggered before starting the
* engine again.
*/
my.start = function start () {
  my.stop();
  for (var i in _startHooks) {
    _startHooks[i]();
  }
  _updateInterval = setInterval(_cycle, 1000/_framerate);
}

/**
* Stops the engine if started, else does nothing.
* 
* Stop hooks are called once the interval is cleared, but only if the engine
* was actually running.
*/
my.stop = function stop() {
  if (my.isStarted()) {
    clearInterval(_updateInterval);
    _updateInterval = null;
    for (var i in _stopHooks) {
      _stopHooks[i]();
    }
  }
}

/**
 * Returns whether or not the game cycle is currently running.
 */
my.isStarted = function isStarted() {
  return _updateInterval !== null;
}

/**
 * Add a hook to the end of the hook list for the game cycle.
 * 
 *  jsgig.addCycleHook(hook[, insertPoint[, before]]);
 * 
 * Optionally pass an index or one of the functions that's already in the
 * list to have the new hook added immediately after the given point.
 * 
 * Optionally pass true to the final parameter to have the hook added _before_
 * the indicated insert point rather than after it.
 */
my.addCycleHook = function addCycleHook(hook, insertPoint, before) {
  if (insertPoint!==undefined) {
    if (insertPoint instanceof Function)
      insertPoint = _cycleHooks.indexOf(insertPoint)
    else if (util.isNumber(insertPoint))
      insertPoint = parseInt(insertPoint)
    else
      throw new TypeError("insertPoint must be a function or number.")
    if (insertPoint < 0 || insertPoint >= _cycleHooks.length)
      throw new util.ValueError("Insert point does not exist.");
    _cycleHooks.splice(insertPoint + (before ? 0 : 1), 0, hook); 
  }
  else {
    _cycleHooks.push(hook);
  }
}

my.getSpace = function getSpace() { return _space; }

/**
 * Add a hook to the end of the start hook list.
 * 
 *  jsgig.addStartHook(hook[, insertPoint[, before]]);
 * 
 * A start hook is a hook that is triggered once when the cycle is started,
 * or after the cycle is restarted after it has been stopped.
 * 
 * Optionally pass an index or one of the functions that's already in the
 * list to have the new hook added immediately after the given point.
 * 
 * Optionally pass true to the final parameter to have the hook added _before_
 * the indicated insert point rather than after it.
 */
my.addStartHook = function addStartHook(hook, insertPoint) {
  if (insertPoint!==undefined) {
    if (insertPoint instanceof Function)
      insertPoint = _startHooks.indexOf(insertPoint)
    else if (util.isNumber(insertPoint))
      insertPoint = parseInt(insertPoint)
    else
      throw new TypeError("insertPoint must be a function or number.")
    if (insertPoint < 0 || insertPoint >= _startHooks.length)
      throw new util.ValueError("Insert point does not exist.");
    _startHooks.splice(insertPoint + (before ? 0 : 1), 0, hook); 
  }
  else {
    _startHooks.push(hook);
  }
}

/**
 * Add a hook to the end of the stop hook list.
 * 
 *  jsgig.addStopHook(hook[, insertPoint[, before]]);
 * 
 * A start hook is a hook that is triggered each time the cycle is stopped.
 * 
 * Optionally pass an index or one of the functions that's already in the
 * list to have the new hook added immediately after the given point.
 * 
 * Optionally pass true to the final parameter to have the hook added _before_
 * the indicated insert point rather than after it.
 */
my.addStopHook = function addStopHook(hook, insertPoint) {
  if (insertPoint!==undefined) {
    if (insertPoint instanceof Function)
      insertPoint = _stopHooks.indexOf(insertPoint)
    else if (util.isNumber(insertPoint))
      insertPoint = parseInt(insertPoint)
    else
      throw new TypeError("insertPoint must be a function or number.")
    if (insertPoint < 0 || insertPoint >= _stopHooks.length)
      throw new util.ValueError("Insert point does not exist.");
    _stopHooks.splice(insertPoint + (before ? 0 : 1), 0, hook); 
  }
  else {
    _stopHooks.push(hook);
  }
}

/**
* Change the current framerate.
* 
* If no value is passed then the current framerate is assumed.
* 
* This function can be called before `jsgig.init()`.
*  
* Default value is 30. 
*/
my.setFramerate = function setFramerate(fps) {
  if (fps!==undefined) {
    if (!util.isNumber(fps))
      throw new TypeError("fps must be a number.");
    fps = parseFloat(fps);
    if (fps <= 0)
      throw new util.ValueError("fps must be at least 1.");
    _framerate = fps
  }
  if (my.isStarted()) {
    my.start();
  }
}

/**
* Return current framerate.
*/
my.getFramerate = function getFramerate() {
  return _framerate;
}

// Main update cycle interval function.
// Calls all cycle hooks.
function _cycle () {
  for (var i in _cycleHooks) {
    _cycleHooks[i]();
  }
}

/**
 * Constitutes the "update" phase of the normal game cycle.
 * This calls update() the space, which in turn calls update() on all its
 * contents.
 */
my.update = function update () {
  _space.update();
}

/**
 * Constitutes the "draw" phase of the normal game cycle.
 * This calls draw(jsgig.screen.getElement(), [0,0]) on the space, which in
 * turn draws all its contents.
 */
my.draw = function draw () {
  _space.draw(my.screen.getElement(), [0,0]);
}

/* Classes */

/**
* Represents a rectangle.
* This class can be used before `jsgig.init()` is called.
*/
my.Rect = util.extend(
  Object,
  
  function Rect (x, y, w, h) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
  },
    
  function toArray () {
    return [this.x, this.y, this.w, this.h];
  },
    
  function copy () {
    return my.Rect(this.x, this.y, this.w, this.h);
  },
    
  function getLeft() {
    return this.h >= 0 ? this.x : this.x + this.w;
  },
  
  function getRight() {
    return this.w >= 0 ? this.x + this.w : this.x;
  },
  
  function getTop() {
    return this.w >= 0 ? this.y : this.y + this.h;
  },
  
  function getBottom() {
    return this.h >= 0 ? this.y + this.h : this.y;
  },
  
  function getPos() {
    return [this.x, this.y];
  },
  
  function getSize() {
    return [this.w, this.h];
  },
  
  function getCenter() {
    return [this.x+this.w*0.5, this.y+this.h*0.5];
  }
);

my.Rect.fromAuto = function /* static */ fromAuto (rect) {
  if (rect instanceof Array) {
    if (rect.length == 2) {
      return my.Rect.fromArray2(rect);
    }
    else if (rect.length == 4) {
      return my.Rect.fromArray4(rect);
    }
    else {
      throw ("Input Array must be of length 2 or 4.");
    }
  }
  else if (rect instanceof my.Rect) {
    return rect.copy();
  }
  else {
    throw ("Input must be Array or my.Rect object.");
  }
}

my.Rect.fromArray4 = function /* static */ fromArray4 (array) {
  return my.Rect(array[0], array[1], array[2], array[4]);
}
  
my.Rect.fromArray2 = function /* static */ fromArray2 (array) {
  return my.Rect(array[0][0], array[0][1], array[1][0], array[1][1]);
}
  
my.Rect.fromArrays = function /* static */ fromArrays (pos, size) {
  return my.Rect(pos[0], pos[1], size[0], size[1]);
}

/**
 * Base exception type for all JSGIG-originated errors.
 */
my.JSGIGError = util.extend(
  Error,
  function JSGIGError(message) {
    Error.apply(this, arguments);
    this.message = message;
  },
  {
    name: "jsgig.JSGIGError"
  }
);

// End of module 'jsgig'
return my; }({})); }